#include "app_main_controller.h"

#include <HardwareSerial.h>
#include <M5EPD.h>
#include <NTPClient.h>
#include <WiFi.h>

#define APP_CONFIG_PATH "/config.json"

#define APP_TIMEZONE_OFFSET (8 * 60 * 60)

#define WIFI_RETRY_DELAY 100

static constexpr char KEY_WIFI_AP_SSID[] = "AP_SSID";
static constexpr char KEY_WIFI_AP_PSK[] = "AP_PSK";
static constexpr char KEY_AMAP_API_KEY[] = "AMAP_KEY";

constexpr char WIFI_AP_SSID[] = "...";
constexpr char WIFI_AP_PSK[] = "...";
constexpr char AMAP_API_KEY[] = "...";

AppMainController::AppMainController()
    : configStore_(APP_CONFIG_PATH),
      sensorDevice_(M5.SHT30),
      rtcDevice_(M5.RTC) {
  mainView_.SetRtcDevice(&rtcDevice_);
}

#define APP_CHECK(expr)                                                        \
  if (!expr) {                                                                 \
    Serial.printf("APP_CHECK(%s): failed, at %s:%d!\r\n", #expr, __FUNCTION__, \
                  __LINE__);                                                   \
    return {};                                                                 \
  } else {                                                                     \
    Serial.printf("APP_CHECK(%s): success!\r\n", #expr);                       \
  }

bool AppMainController::Initialize() {
  M5.begin();  // 初始化 M5Paper
  APP_CHECK(InitCanvas());

  // APP_CHECK(InitConfigs());
  APP_CHECK(ShowConfigLoading());
  APP_CHECK(LoadConfigs());
  delay(1000);

  APP_CHECK(ShowWifiConnecting());
  APP_CHECK(ConnectToWifi());
  delay(1000);

  APP_CHECK(SetUpAmapApiKey());
  APP_CHECK(ShowLocationRequesting());
  APP_CHECK(RequestAmapLocation());
  delay(1000);

  APP_CHECK(ShowWeatherRequesting());
  APP_CHECK(RequestAmapWeather());
  delay(1000);

  APP_CHECK(ShowTimeRequesting());
  APP_CHECK(RequestNtpTimestamp());
  delay(1000);

  APP_CHECK(ShowTimeUpdating());
  APP_CHECK(UpdateRtcDateTime());

  return true;
}

bool AppMainController::UpdateSensorState() {
  if (!UpdateSensorData()) {
    Serial.printf("Update sensor data failed!\r\n");
    return false;
  }
  this->mainView_.UpdateSensorState(this->sensorModel_);
  return true;
}

bool AppMainController::UpdateWeatherState() {
  if (!wifiConnected_) {
    ConnectToWifi();
  }
  APP_CHECK(RequestAmapWeather());
  return true;
}

bool AppMainController::UpdateDateTime() {
  if (!wifiConnected_) {
    APP_CHECK(ConnectToWifi());
  }
  APP_CHECK(RequestNtpTimestamp());
  APP_CHECK(UpdateRtcDateTime());
  return true;
}

bool AppMainController::ShowMainView() {
  this->mainView_.DrawMainView();
  return true;
}

bool AppMainController::ShowConfigLoading() {
  this->mainView_.DrawConfigLoadView();
  return true;
}

bool AppMainController::InitConfigs() {
  Serial.println("******************************************************");
  if (!configStore_.PutConfig(KEY_WIFI_AP_SSID, WIFI_AP_SSID)) {
    Serial.printf("Store config %s: failed!\r\n", KEY_WIFI_AP_SSID);
    return false;
  }
  if (!configStore_.PutConfig(KEY_WIFI_AP_PSK, WIFI_AP_PSK)) {
    Serial.printf("Store config %s: failed!\r\n", KEY_WIFI_AP_PSK);
    return false;
  }
  if (!configStore_.PutConfig(KEY_AMAP_API_KEY, AMAP_API_KEY)) {
    Serial.printf("Store config %s: failed!\r\n", KEY_AMAP_API_KEY);
    return false;
  }
  return true;
}

bool AppMainController::LoadConfigs() {
  String value;
  Serial.println("******************************************************");
  configStore_.PrintConfigs();
  if (!configStore_.GetConfig(KEY_WIFI_AP_SSID, value)) {
    Serial.printf("Load config %s: failed!\r\n", KEY_WIFI_AP_SSID);
    return false;
  }
  configModel_.wifiSsid = value;
  Serial.printf("Load config %s: %s success!\r\n", KEY_WIFI_AP_SSID,
                value.c_str());

  if (!configStore_.GetConfig(KEY_WIFI_AP_PSK, value)) {
    Serial.printf("Load config %s: failed!\r\n", KEY_WIFI_AP_PSK);
    return false;
  }
  configModel_.wifiPsk = value;
  Serial.printf("Load config %s: %s success!\r\n", KEY_WIFI_AP_PSK,
                value.c_str());

  if (!configStore_.GetConfig(KEY_AMAP_API_KEY, value)) {
    Serial.printf("Load config %s: failed!\r\n", KEY_AMAP_API_KEY);
    return false;
  }
  configModel_.amapApiKey = value;
  Serial.printf("Load config %s: %s success!\r\n", KEY_AMAP_API_KEY,
                value.c_str());
  return true;
}

bool AppMainController::ShowWifiConnecting() {
  this->mainView_.DrawWifiConnectView();
  return true;
}

bool AppMainController::ConnectToWifi(int timeout) {
  Serial.println("******************************************************");
  Serial.printf("Connecting to Wi-Fi hotspot %s!\r\n",
                configModel_.wifiSsid.c_str());
  WiFi.begin(configModel_.wifiSsid, configModel_.wifiPsk);

  int retryTime = 0;
  while (WiFi.status() != WL_CONNECTED && retryTime < timeout) {
    delay(WIFI_RETRY_DELAY);
    retryTime += WIFI_RETRY_DELAY;
    Serial.print(".");
  }
  Serial.println("");

  if (retryTime >= timeout) {
    Serial.println("Timeout!");
    return false;
  }

  Serial.println("WiFi connected.");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  wifiConnected_ = true;
  return true;
}

bool AppMainController::SetUpAmapApiKey() {
  locationService_.SetAmapApiKey(configModel_.amapApiKey.c_str());
  weatherService_.SetAmapApiKey(configModel_.amapApiKey.c_str());
  return true;
}

bool AppMainController::ShowLocationRequesting() {
  this->mainView_.DrawLocationRequestView();
  return true;
}

bool AppMainController::RequestAmapLocation() {
  // ipify IP地址 查询
  IPAddress ipAddress{};
  if (!locationService_.QueryPublicIpAddress(ipAddress)) {  // 查询公网IP地址
    Serial.println("Get public IP failed!");
    return false;
  }
  Serial.printf("Query public IP success: %s\r\n",
                ipAddress.toString().c_str());

  // 高德IP定位
  webapi::AmapLocation locResponse;
  if (!locationService_.QueryGeoLocation(locResponse, ipAddress)) {  // IP定位
    Serial.println("Get Geo location failed!");
    return false;
  }
  Serial.printf("adcode = %d\r\n", locResponse.adcode);

  auto location = webapi::GetRectangleCenter(locResponse.rectangle);
  Serial.printf("location = %.2f, %.2f\r\n", location.longitude,
                location.latitude);

  // 更新Model数据
  currentLocation_.longitude = location.longitude;
  currentLocation_.latitude = location.latitude;
  currentLocation_.adcode = locResponse.adcode;
  currentLocation_.origin = locResponse;
  mainView_.UpdateLocationState(currentLocation_);
  return true;
}

bool AppMainController::ShowWeatherRequesting() {
  this->mainView_.DrawWeatherRequestView();
  return true;
}

bool AppMainController::RequestAmapWeather() {
  // 高德天气
  AppRealtimeWeather rtWeather;
  if (!weatherService_.QueryRealtimeWeather(
          rtWeather, currentLocation_.adcode)) {  // 查询实时天气
    Serial.println("Query realtime weather failed!");
    return false;
  }

  AppForecastWeather fcWeather;
  if (!weatherService_.QueryForecastWeather(
          fcWeather, currentLocation_.adcode)) {  // 查询预报天气
    Serial.println("Query forecast weather failed!");
    return false;
  }

  // 更新Model数据
  this->realtimeWeather_ = rtWeather;
  this->forecastWeather_ = fcWeather;
  this->mainView_.UpdateRealtimeWeather(rtWeather);
  this->mainView_.UpdateForecastWeather(fcWeather);
  return true;
}

bool AppMainController::ShowTimeUpdating() {
  this->mainView_.DrawTimeUpdateView();
  return true;
}

bool AppMainController::ShowTimeRequesting() {
  this->mainView_.DrawTimeRequestView();
  return true;
}

bool AppMainController::RequestNtpTimestamp() {
  // NTP 获取时间
  WiFiUDP ntpUDP;
  NTPClient timeClient(ntpUDP);
  timeClient.setTimeOffset(APP_TIMEZONE_OFFSET);
  if (!timeClient.update()) {
    Serial.println("NTP request failed!");
    return false;
  }

  Serial.print("NTP time: ");
  Serial.println(timeClient.getFormattedTime());

  HalRtcDevice::Date date;
  HalRtcDevice::Time time;
  time_t timestamp = timeClient.getEpochTime();  // seconds since Jan. 1, 1970
  if (!HalRtcDevice::ConvertDateTime(date, time, timestamp)) {
    Serial.println("Convert timestamp to date time failed!");
    return false;
  }

  // 更新Model数据
  this->dateTime_.date = date;
  this->dateTime_.time = time;
  return true;
}

bool AppMainController::UpdateRtcDateTime() {
  // RTC 日期时间更新
  if (!rtcDevice_.SetDate(this->dateTime_.date)) {
    Serial.println("RTC set date failed!\r\n");
    return false;
  }
  if (!rtcDevice_.SetTime(this->dateTime_.time)) {
    Serial.println("RTC set time failed!\r\n");
    return false;
  }
  return true;
}

bool AppMainController::UpdateSensorData() {
  if (!sensorDevice_.Measure()) {
    Serial.printf("Sensor measure failed!\r\n");
    return false;
  }
  this->sensorModel_.temp = this->sensorDevice_.GetTemperature();
  this->sensorModel_.humi = this->sensorDevice_.GetHumidity();
  Serial.printf("Sensor Temperature: %.2f*C  Humidity: %.2f%%\r\n",
                this->sensorModel_.temp, this->sensorModel_.humi);
  return true;
}

bool AppMainController::InitCanvas() { return mainView_.Initialize(); }
